package de.justphil.tcg.tcgserver.commons.geo;

import org.slf4j.Logger;

import de.justphil.tcg.tcgserver.commons.config.RestConfig;
import de.justphil.tcg.tcgserver.commons.domain.wrappers.GeoLocation;
import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;

public class GeoCalculation {
	
	private static final Logger log = restLogger(GeoCalculation.class);

	public GeoLocation[] getRegion(GeoLocation geo, double radius) {
		
		log.debug("getRegionGeoLocations()");
		log.debug("geo\t=> {}", geo);
		
		double d = radius;
		double R = RestConfig.EARTH_RADIUS;
		
		double bNorth	= Math.toRadians( RestConfig.BEARING_NORTH );
		double bEast	= Math.toRadians( RestConfig.BEARING_EAST );
		double bSouth	= Math.toRadians( RestConfig.BEARING_SOUTH );
		double bWest	= Math.toRadians( RestConfig.BEARING_WEST );
		
		log.debug("Bearing north deg => {}, rad => {}", RestConfig.BEARING_NORTH, bNorth);
		log.debug("Bearing east deg => {}, rad => {}", RestConfig.BEARING_EAST, bEast);
		log.debug("Bearing south deg => {}, rad => {}", RestConfig.BEARING_SOUTH, bSouth);
		log.debug("Bearing west deg => {}, rad => {}", RestConfig.BEARING_WEST, bWest);
		
		double radRefLat = Math.toRadians(geo.getLat());
		double radRefLon = Math.toRadians(geo.getLon());
		
		// Bearing North
		double radNorthLat = 	Math.asin(
									Math.sin( radRefLat ) * Math.cos( d/R ) +
									Math.cos( radRefLat ) * Math.sin( d/R ) * Math.cos( bNorth )
								);
		
		double radNorthLon = 	radRefLon + Math.atan2(
											Math.sin( bNorth ) * Math.sin( d/R ) * Math.cos( radRefLat ),
							                Math.cos( d/R ) - Math.sin( radRefLat ) * Math.sin( radNorthLat )
								);
		
		// Bearing East
		double radEastLat = 	Math.asin(
									Math.sin( radRefLat ) * Math.cos( d/R ) +
									Math.cos( radRefLat ) * Math.sin( d/R ) * Math.cos( bEast )
								);
		
		double radEastLon = 	radRefLon + Math.atan2(
											Math.sin( bEast ) * Math.sin( d/R ) * Math.cos( radRefLat ),
							                Math.cos( d/R ) - Math.sin( radRefLat ) * Math.sin( radEastLat )
								);
		
		// Bearing South
		double radSouthLat = 	Math.asin(
									Math.sin( radRefLat ) * Math.cos( d/R ) +
									Math.cos( radRefLat ) * Math.sin( d/R ) * Math.cos( bSouth )
								);
		
		double radSouthLon = 	radRefLon + Math.atan2(
											Math.sin( bSouth ) * Math.sin( d/R ) * Math.cos( radRefLat ),
							                Math.cos( d/R ) - Math.sin( radRefLat ) * Math.sin( radSouthLat )
								);
		
		// Bearing West
		double radWestLat = 	Math.asin(
									Math.sin( radRefLat ) * Math.cos( d/R ) +
									Math.cos( radRefLat ) * Math.sin( d/R ) * Math.cos( bWest )
								);
		
		double radWestLon = 	radRefLon + Math.atan2(
											Math.sin( bWest ) * Math.sin( d/R ) * Math.cos( radRefLat ),
							                Math.cos( d/R ) - Math.sin( radRefLat ) * Math.sin( radWestLat )
								);
		
		
		
		// Radians -> Degrees
		double degNorthLat	= Math.toDegrees(radNorthLat);
		double degNorthLon	= Math.toDegrees(radNorthLon);
		
		double degEastLat	= Math.toDegrees(radEastLat);
		double degEastLon	= Math.toDegrees(radEastLon);
		
		double degSouthLat	= Math.toDegrees(radSouthLat);
		double degSouthLon	= Math.toDegrees(radSouthLon);
		
		double degWestLat	= Math.toDegrees(radWestLat);
		double degWestLon	= Math.toDegrees(radWestLon);
		
		
		
		GeoLocation[] regionGeoLocations = new GeoLocation[] {
				
				new GeoLocation(degNorthLat,	degNorthLon),
				new GeoLocation(degEastLat,		degEastLon),
				new GeoLocation(degSouthLat,	degSouthLon),
				new GeoLocation(degWestLat,		degWestLon),
				
		};
		
		log.debug("north\t=> {}", 	regionGeoLocations[0]);
		log.debug("east\t=> {}", 	regionGeoLocations[1]);
		log.debug("south\t=> {}", 	regionGeoLocations[2]);
		log.debug("west\t=> {}",	regionGeoLocations[3]);
		
		return regionGeoLocations;
	}
	
	/*
	 * Get the GEO distance between two GPS fixes in kilometers.
	 */
	public double getDistance( GeoLocation g1, GeoLocation g2 ) {
		
		final double R = RestConfig.EARTH_RADIUS;
		
		double lat1 = Math.toRadians( g1.getLat() );
		double lon1 = Math.toRadians( g1.getLon() );
		double lat2 = Math.toRadians( g2.getLat() );
		double lon2 = Math.toRadians( g2.getLon() );
		
		// Spherical Law of Cosines
		double d = 	Math.acos(
						Math.sin( lat1 ) * Math.sin( lat2 ) + 
						Math.cos( lat1 ) * Math.cos( lat2 ) *
						Math.cos( lon2-lon1 )
					) * R;
		
		log.debug("d({}, {}) => {}", new Object[] { g1, g2, d });
		
		return d;
	}
}
